/**
 * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yun.web.activerecord;

import yun.web.enjoy.kit.IPlugin;
import yun.web.enjoy.kit.StrKit;
import yun.web.activerecord.dialect.Dialect;
import yun.web.activerecord.sql.SqlKit;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

/**
 * ActiveRecord plugin.
 * <br>
 * ActiveRecord plugin not support mysql type year, you can use int instead of year.
 * Mysql error message for type year when insert a record: Data truncated for column 'xxx' at row 1
 */
public class ActiveRecordPlugin implements IPlugin {

    protected IDataSourceProvider dataSourceProvider = null;
    protected Boolean devMode = null;

    protected Config config = null;

    protected volatile boolean isStarted = false;
    protected List<Table> tableList = new ArrayList<Table>();

    public ActiveRecordPlugin(String configName, DataSource dataSource, int transactionLevel) {
        if (StrKit.isBlank(configName)) {
            throw new IllegalArgumentException("configName can not be blank");
        }
        if (dataSource == null) {
            throw new IllegalArgumentException("dataSource can not be null");
        }
        this.config = new Config(configName, dataSource, transactionLevel);
    }

    public ActiveRecordPlugin(DataSource dataSource) {
        this(DbKit.MAIN_CONFIG_NAME, dataSource);
    }

    public ActiveRecordPlugin(String configName, DataSource dataSource) {
        this(configName, dataSource, DbKit.DEFAULT_TRANSACTION_LEVEL);
    }

    public ActiveRecordPlugin(DataSource dataSource, int transactionLevel) {
        this(DbKit.MAIN_CONFIG_NAME, dataSource, transactionLevel);
    }

    public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider, int transactionLevel) {
        if (StrKit.isBlank(configName)) {
            throw new IllegalArgumentException("configName can not be blank");
        }
        if (dataSourceProvider == null) {
            throw new IllegalArgumentException("dataSourceProvider can not be null");
        }
        this.dataSourceProvider = dataSourceProvider;
        this.config = new Config(configName, null, transactionLevel);
    }

    public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {
        this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);
    }

    public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider) {
        this(configName, dataSourceProvider, DbKit.DEFAULT_TRANSACTION_LEVEL);
    }

    public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider, int transactionLevel) {
        this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider, transactionLevel);
    }

    public ActiveRecordPlugin(Config config) {
        if (config == null) {
            throw new IllegalArgumentException("Config can not be null");
        }
        this.config = config;
    }

    public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<? extends Model<?>> modelClass) {
        tableList.add(new Table(tableName, primaryKey, modelClass));
        return this;
    }

    public ActiveRecordPlugin addMapping(String tableName, Class<? extends Model<?>> modelClass) {
        tableList.add(new Table(tableName, modelClass));
        return this;
    }

    public ActiveRecordPlugin addSqlTemplate(String sqlTemplate) {
        config.sqlKit.addSqlTemplate(sqlTemplate);
        return this;
    }

    public ActiveRecordPlugin addSqlTemplate(yun.web.enjoy.template.source.ISource sqlTemplate) {
        config.sqlKit.addSqlTemplate(sqlTemplate);
        return this;
    }

    public ActiveRecordPlugin setBaseSqlTemplatePath(String baseSqlTemplatePath) {
        config.sqlKit.setBaseSqlTemplatePath(baseSqlTemplatePath);
        return this;
    }

    public SqlKit getSqlKit() {
        return config.sqlKit;
    }

    public yun.web.enjoy.template.Engine getEngine() {
        return getSqlKit().getEngine();
    }

    /**
     * Set transaction level define in java.sql.Connection
     *
     * @param transactionLevel only be 0, 1, 2, 4, 8
     */
    public ActiveRecordPlugin setTransactionLevel(int transactionLevel) {
        config.setTransactionLevel(transactionLevel);
        return this;
    }


    public ActiveRecordPlugin setShowSql(boolean showSql) {
        config.showSql = showSql;
        return this;
    }

    public ActiveRecordPlugin setDevMode(boolean devMode) {
        this.devMode = devMode;
        config.setDevMode(devMode);
        return this;
    }

    public Boolean getDevMode() {
        return devMode;
    }

    public ActiveRecordPlugin setDialect(Dialect dialect) {
        if (dialect == null) {
            throw new IllegalArgumentException("dialect can not be null");
        }
        config.dialect = dialect;
        if (config.transactionLevel == Connection.TRANSACTION_REPEATABLE_READ && dialect.isOracle()) {
            // Oracle 不支持 Connection.TRANSACTION_REPEATABLE_READ
            config.transactionLevel = Connection.TRANSACTION_READ_COMMITTED;
        }
        return this;
    }

    public ActiveRecordPlugin setContainerFactory(IContainerFactory containerFactory) {
        if (containerFactory == null) {
            throw new IllegalArgumentException("containerFactory can not be null");
        }
        config.containerFactory = containerFactory;
        return this;
    }

    public ActiveRecordPlugin setDbProFactory(IDbProFactory dbProFactory) {
        if (dbProFactory == null) {
            throw new IllegalArgumentException("dbProFactory can not be null");
        }
        config.dbProFactory = dbProFactory;
        return this;
    }

    /**
     * 当使用 create table 语句创建用于开发使用的数据表副本时，假如create table 中使用的
     * 复合主键次序不同，那么MappingKitGeneretor 反射生成的复合主键次序也会不同。
     * <p>
     * 而程序中类似于 model.deleteById(id1, id2) 方法中复合主键次序与需要与映射时的次序
     * 保持一致，可以在MappingKit 映射完成以后通过调用此方法再次强制指定复合主键次序
     *
     * <pre>
     * Example:
     * ActiveRecrodPlugin arp = new ActiveRecordPlugin(...);
     * _MappingKit.mapping(arp);
     * arp.setPrimaryKey("account_role", "account_id, role_id");
     * me.add(arp);
     * </pre>
     */
    public void setPrimaryKey(String tableName, String primaryKey) {
        for (Table table : tableList) {
            if (table.getName().equalsIgnoreCase(tableName.trim())) {
                table.setPrimaryKey(primaryKey);
            }
        }
    }

    public boolean start() {
        if (isStarted) {
            return true;
        }
        if (config.dataSource == null && dataSourceProvider != null) {
            config.dataSource = dataSourceProvider.getDataSource();
        }
        if (config.dataSource == null) {
            throw new RuntimeException("ActiveRecord start error: ActiveRecordPlugin need DataSource or DataSourceProvider");
        }

        config.sqlKit.parseSqlTemplate();

        new TableBuilder().build(tableList, config);
        DbKit.addConfig(config);
        isStarted = true;
        return true;
    }

    public boolean stop() {
        DbKit.removeConfig(config.getName());
        isStarted = false;
        return true;
    }


    public Config getConfig() {
        return config;
    }
}






